package pl.enigmatic.validation;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import pl.enigmatic.validation.util.Util;


public abstract class ValidatingForm<Widget, PopupPanel> implements ErrorPresenter {

	protected class Notifier implements pl.enigmatic.Notifier {

		private final String property;

		private Notifier(final String property) {
			this.property = property;
		}

		@Override
		public void notifyChange() {
			for (final String errorCode : widgetToErrors.get(property)) {
				if (visualized.containsKey(errorCode)) {
					clearErrors(visualized.get(errorCode));
				}
			}
			if (!validate(property)) {
				final Widget widget = widgets.get(property);
				for (final String errorCode : errors.get(property)) {
					final PopupPanel popup = showError(getErrorMessage(errorCode), widget);
					visualized.put(errorCode, popup);
				}
			}
		}
	}

	/** property ==> widget */
	private final Map<String, Widget> widgets = new HashMap<String, Widget>();
	/** error code ==> validator */
	@SuppressWarnings("rawtypes")
	private final Map<String, SimpleValidator> validators = new HashMap<String, SimpleValidator>();
	/** error code ==> property */
	private final Map<String, String> errorToWidget = new HashMap<String, String>();
	/** property ==> error codes */
	private final Map<String, Set<String>> widgetToErrors = new HashMap<String, Set<String>>();

	/** property ==> error codes */
	private final Map<String, Set<String>> errors = new HashMap<String, Set<String>>();
	/** error code ==> error panel */
	private final Map<String, PopupPanel> visualized = new HashMap<String, PopupPanel>();

	protected abstract void addListener(Widget widget, pl.enigmatic.Notifier notifier);

	protected abstract PopupPanel showError(String message, Widget widget);

	protected abstract void clearErrors(PopupPanel popup);

	protected <W extends Widget> boolean wrap(final String property, final W widget) {
		if (widgets.containsKey(property)) {
			if (widgets.get(property) != widget) { return false; }
		} else {
			widgets.put(property, widget);
			widgetToErrors.put(property, new HashSet<String>());
			errors.put(property, new HashSet<String>());
			addListener(widget, new Notifier(property));
		}
		return true;
	}

	public <W extends Widget> boolean addSimpleValidation(final String property, final String errorCode, final W widget,
			final SimpleValidator<W> validator) {
		wrap(property, widget);
		if (!errorToWidget.keySet().contains(errorCode)) {
			validators.put(errorCode, validator);
			errorToWidget.put(errorCode, property);
			widgetToErrors.get(property).add(errorCode);
			return true;
		}
		return false;
	}

	public <W extends Widget> boolean addValidation(final String property, final String errorCode, final W widget, final Validator validator) {
		return addSimpleValidation(property, errorCode, widget, new SimpleValidator<W>() {

			@Override
			public boolean isValueValid(final W val) {
				return validator.isValid();
			}
		});
	}

	@SuppressWarnings("unchecked")
	public <T, W extends Widget> boolean addHasValueValidation(final String property, final String errorCode, final HasValueValidator<T> validator) {
		return addValidation(property, errorCode, (Widget) validator.getObject(), (Validator) validator);
	}

	@SuppressWarnings("unchecked")
	public <T> boolean addValidation(final String property, final String errorCode, final HasValue<T> valued, final SimpleValidator<T> validator) {
		return addValidation(property, errorCode, (Widget) valued, Util.create(valued, validator));
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public boolean validate(final String property) {
		errors.get(property).clear();
		boolean valid = true;
		for (final String errorCode : widgetToErrors.get(property)) {
			final SimpleValidator validator = validators.get(errorCode);
			if (!validator.isValueValid(widgets.get(property))) {
				errors.get(property).add(errorCode);
				valid = false;
			}
		}
		return valid;
	}

	public boolean validate() {
		boolean valid = true;
		for (final String property : widgets.keySet()) {
			valid = validate(property) && valid;
		}
		return valid;
	}

	public void clearErrors() {
		for (final PopupPanel popup : visualized.values()) {
			clearErrors(popup);
		}
		visualized.clear();
	}

	public void showErrors() {
		clearErrors();
		for (final Entry<String, Set<String>> e : errors.entrySet()) {
			final Widget widget = widgets.get(e.getKey());
			for (final String errorCode : e.getValue()) {
				final PopupPanel popup = showError(getErrorMessage(errorCode), widget);
				visualized.put(errorCode, popup);
			}
		}
	}

	@Override
	public void addError(final String property, final String errorCode) {
		errors.get(property).add(errorCode);
	}

	protected String getErrorMessage(final String code) {
		return code;
	}

}
